﻿// poj2441 Arrange the Bulls.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <vector>
//#include <unordered_map>

using namespace std;

/*

http://poj.org/problem?id=2441

排列公牛
时间限制：4000MS 内存限制：65536K
总提交量。7130 已接受：2712
任务描述

农夫约翰逊的公牛们非常喜欢打篮球。但它们都不愿意和其他公牛打篮球，因为它们认为其他公牛都很弱。
农场主约翰逊有N头牛（我们把牛从1到N编号）和M个谷仓（我们把谷仓从1到M编号），这是他的公牛们的篮球场。
然而，他的公牛都很任性，它们只喜欢在一些特定的牛棚里玩，不愿意和其他的牛棚共用一个。

所以农场主约翰逊很难安排他的公牛，他希望你能帮助他。当然，找到一个解决方案很容易，但你的任务是找到有多少个解决方案。

你应该知道，一个解决方案是指每头公牛都能在他喜欢的牛棚里打篮球，而且没有两头公牛共用一个牛棚的情况。

为了使问题变得简单一些，我们假设解决方案的数量不会超过10000000。
输入

第一行输入包含两个整数N和M（1 <= N <= 20，1 <= M <= 20）。然后是N行。第i行首先包含一个整数P（1 <= P <= M），指的是奶牛i喜欢玩的谷仓的数量。
然后是P个整数，给出P个牛舍的数量。
输出

在一行中打印一个整数，这是解决方案的数量。
输入样本

3 4
2 1 4
2 1 3
2 2 4
样本输出
4

12 20
5 1 2 3 12 13
5 2 3 4 11 16
5 3 4 5 17 18
5 4 5 6 16 19
5 5 6 7 17 19
5 6 7 8 11 12
5 7 8 9 13 15
5 8 9 10 15 16
5 9 10 11 17 18
5 10 11 12 18 19
5 9 10 11 17 18
5 10 11 12 18 19

*/


const int N = 1 << 20;
int dp[2][N+10];
int p; int ans;
int n, m;
//int cows[21][21];
vector<int> cows[21];


void solve() {
	for (int j = 0; j < (1 << m); j++) {
		dp[0][j] = 1;
	}
	//dp[0][0] = 1;
	int limit = (1 << m);

	int curr = 1; int prev = 0;
	for (int i = 1; i <= n; i++) {
		memset(&dp[curr][0], 0, sizeof(dp[curr]));
		for (int j = 1; j < limit; j++) {
			if (dp[prev][j] == 0) { continue; }
			for (int z = 0; z < cows[i].size(); z++) {
				int v = cows[i][z];
				if (((j >> v) & 1) == 0) continue;
				int flag = j & (~(1 << v));
				if (dp[prev][flag]  ) {
					dp[curr][j] += dp[prev][flag];
				}
			}
		}
		swap(curr,prev);
	}

	printf("%d\n", dp[prev][(1 << m) - 1]);
}

 

int main()
{
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++) {

		scanf("%d", &p);
		for (int j = 0; j < p; j++) {
			int a;
			//cin >> a;
			scanf("%d", &a);
			//cows[i][a-1] = 1;
			cows[i].push_back(a - 1);
		}
	}

	solve();

	return 0;
}

 